#include "zrpc/http/http_client.h"

#include "zrpc/net/socket.h"
namespace zrpc {
HttpClient::HttpClient(EventLoop *loop) : loop_(loop), index_(0) {}

void HttpClient::HttpClientTimerCallback(const int64_t index) {
  auto it = tcp_conns_.find(index);
  assert(it != tcp_conns_.end());
  auto conn = it->second.lock();
  if (conn) {
    conn->Shutdown();
  }
}

void HttpClient::GetUrl(const char *ip, int16_t port, const std::string &url,
                        const std::string &host,
                        const std::shared_ptr<TcpConnection> &conn,
                        const std::any &context, HttpCallBack &&callback) {
  std::shared_ptr<HttpRequest> request(new HttpRequest());
  request->SetIndex(++index_);
  request->SetQuery(url);
  request->SetMethod(HttpRequest::kGet);
  request->AddHeader("Host", host);

  std::shared_ptr<TcpClient> client(new TcpClient(loop_, ip, port, request));
  client->CloseRetry();
  client->SetConnectionCallback(
      std::bind(&HttpClient::OnConnection, this, std::placeholders::_1));
  client->SetMessageCallback(std::bind(&HttpClient::OnMessage, this,
                                       std::placeholders::_1,
                                       std::placeholders::_2));
  client->Connect();

  tcp_clients_[index_] = client;
  tcp_conns_[index_] = conn;
  http_callbacks_[index_] = callback;
  any_callbacks_[index_] = context;
  timers_[index_] = loop_->RunAfter(
      kTimer, false,
      std::bind(&HttpClient::HttpClientTimerCallback, this, index_));
}

void HttpClient::PostUrl(const char *ip, int16_t port, const std::string &url,
                         const std::string &body, const std::string &host,
                         const std::string &type,
                         const std::shared_ptr<TcpConnection> &conn,
                         const std::any &context, HttpCallBack &&callback) {
  std::shared_ptr<HttpRequest> request(new HttpRequest());
  request->SetIndex(++index_);
  request->SetQuery(url);
  request->SetBody(body);
  request->SetMethod(HttpRequest::kPost);
  request->AddHeader("Host", host);
  request->AddHeader("Content-Type", type);
  request->AddHeader("Content-Length", std::to_string(body.size()));

  std::shared_ptr<TcpClient> client(new TcpClient(loop_, ip, port, request));
  client->CloseRetry();
  client->SetConnectionCallback(
      std::bind(&HttpClient::OnConnection, this, std::placeholders::_1));
  client->SetMessageCallback(std::bind(&HttpClient::OnMessage, this,
                                       std::placeholders::_1,
                                       std::placeholders::_2));
  client->Connect();

  tcp_clients_[index_] = client;
  tcp_conns_[index_] = conn;
  http_callbacks_[index_] = callback;
  any_callbacks_[index_] = context;
  timers_[index_] = loop_->RunAfter(
      kTimer, false,
      std::bind(&HttpClient::HttpClientTimerCallback, this, index_));
}

void HttpClient::OnConnection(const std::shared_ptr<TcpConnection> &conn) {
  std::shared_ptr<HttpRequest> request =
      std::any_cast<std::shared_ptr<HttpRequest>>(conn->GetContext());
  if (conn->Connected()) {
    request->AppendToBuffer(conn->OutputBuffer());
    conn->SendPipe();

    std::shared_ptr<HttpContext> c(new HttpContext());
    conn->SetContext1(c);
  } else {
    auto it = timers_.find(request->GetIndex());
    assert(it != timers_.end());
    loop_->CancelAfter(it->second);

    size_t n = tcp_clients_.erase(request->GetIndex());
    assert(n == 1);
    n = timers_.erase(request->GetIndex());
    assert(n == 1);
    n = tcp_conns_.erase(request->GetIndex());
    assert(n == 1);
    n = http_callbacks_.erase(request->GetIndex());
    assert(n == 1);
    n = any_callbacks_.erase(request->GetIndex());
    assert(n == 1);
  }
}

void HttpClient::OnMessage(const std::shared_ptr<TcpConnection> &conn,
                           Buffer *buffer) {
  std::shared_ptr<HttpContext> context =
      std::any_cast<std::shared_ptr<HttpContext>>(conn->GetContext1());
  std::shared_ptr<HttpRequest> request =
      std::any_cast<std::shared_ptr<HttpRequest>>(conn->GetContext());

  auto it = tcp_conns_.find(request->GetIndex());
  assert(it != tcp_conns_.end());

  auto iter = http_callbacks_.find(request->GetIndex());
  assert(iter != http_callbacks_.end());

  auto iterr = any_callbacks_.find(request->GetIndex());
  assert(iterr != any_callbacks_.end());

  if (!context->ParseResponse(buffer)) {
    iter->second(conn, context->GetResponse(), it->second, iterr->second);
    return;
  }

  if (context->GotAll()) {
    iter->second(conn, context->GetResponse(), it->second, iterr->second);
  }
}
}  // namespace zrpc